#include "fileman.h"
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <set>
#include <assert.h>
#include "mossutil.h"

using namespace std;

class lock
{
public:
	lock() { }
	~lock() { }
};

struct FileCache
{
	int size;
	OnlineStorage** blkstor;
	int * cached;
	int blocks;
	int opens;
	string filename;
	string pathname;
	int maxblocks;
	~FileCache()
	{
		/*if (blkstor)
			delete[] blkstor;*/
	}
};

struct DirectoryCache
{
	string path;
	map<string,FileCache> files;
	set<string> folders;
};


void FileManager::addOnlineStorage(OnlineStorage* stor)
{
	if (!connected)
		vecStors.push_back(stor);
}

FileManager::FileManager()
{
	connected = false;
}

int FileManager::connect()
{
	int ret = 0;
	if (!connected)
	{
		connected = true;
		for(vector<OnlineStorage*>::iterator it = vecStors.begin();
			it != vecStors.end(); ++it)
		{
			if (ret = (*it)->connect())
				return ret;
		}
	}
	return ret;
}

void FileManager::disconnect()
{
	if (connected)
	{
		connected = false;
		for(vector<OnlineStorage*>::iterator it = vecStors.begin();
			it != vecStors.end(); ++it)
			(*it)->disconnect();
	}
}

struct OnlineFile* FileManager::open(const char* pathname)
{
	lock l;
	int ret;
	string path; string name;
	assert(parsePath(string(pathname),path,name));
	if (ret = ensure_dir(path.c_str()))
		return NULL;
	DirectoryCache* dir = find_dir(path.c_str())->second;
	map<string,FileCache>::iterator it = dir->files.find(string(name));
	if (it == dir->files.end())	
	{
		int blocknum = 64;
		FileCache fileCache;
		fileCache.blocks = 0;
		fileCache.blkstor = NULL;
		fileCache.size = 0;
		fileCache.opens = 0;
		fileCache.filename = name;
		fileCache.pathname = string(pathname);

		fileCache.blkstor = new OnlineStorage* [blocknum];
		memset(fileCache.blkstor,0,blocknum * sizeof(OnlineStorage*));
		fileCache.cached = new int[blocknum];
		memset(fileCache.cached,0,blocknum*sizeof(int));
		fileCache.maxblocks = blocknum;

		dir->files.insert(make_pair(string(name),fileCache));
		it = dir->files.find(string(name));
	}
	it->second.opens++;
	return (struct OnlineFile*)&(it->second);
}
int FileManager::read(struct OnlineFile* file,char* buffer,int offset,int size)
{
	lock l;
	struct FileCache* f = (struct FileCache*) file;
	int count = 0;int numread = 0;
	while(size > 0)
	{
		int block = (offset+numread) / blockSize;
		int blockOffset = (offset+numread) % blockSize;
		count = blockSize - blockOffset;
		count = count > size ? size : count;
		if (block >= f->blocks) break;
		ensure_block(f,block);

		string tmpfile = getLocalFileName(f->pathname,block);//get temp
		FILE* fp = fopen(tmpfile.c_str(),"rb");
		fseek(fp,blockOffset,SEEK_SET);
		fread(buffer+numread,1,count,fp);
		fclose(fp);

		size -= count;
		numread += count;
	}
	return 0;
}
int FileManager::write(struct OnlineFile* file,char* buffer,int offset,int size)
{
	lock l;
	struct FileCache* f = (struct FileCache*) file;
	int count = 0;int numread = 0;
	while(size > 0)
	{
		int block = (offset+numread) / blockSize;
		int blockOffset = (offset+numread) % blockSize;
		count = blockSize - blockOffset;
		count = count > size ? size : count;
		ensure_block(f,block);

		f->cached[block] = 2;
		f->size = max(f->size,blockSize * (f->blocks - 1) + blockOffset + count);

		string tmpfile = getLocalFileName(f->pathname,block);//get temp
		FILE* fp = fopen(tmpfile.c_str(),"r+b");
		fseek(fp,blockOffset,SEEK_SET);
		fwrite(buffer+numread,1,count,fp);
		fclose(fp);

		size -= count;
		numread += count;
	}
	return 0;
}
void FileManager::flush(struct FileCache* f)
{
	char* buffer = new char[blockSize];
	for(int i=0;i < f->blocks ; ++i)
		if (f->cached[i] == 2)
		{
			string tmp;
			string pathname;
			tmp = getLocalFileName(f->pathname,i);
			assert(getFullname(f->pathname,pathname,i));
			FILE* fp = fopen(tmp.c_str(),"rb");
			int size = i == f->blocks - 1 ? f->size % blockSize : blockSize;
			if (fp)
			{
				fread(buffer,1,size,fp);
				fclose(fp);
			}
			else
				memset(buffer,0,size);
			f->blkstor[i]->put(pathname.c_str(),buffer,size);
			f->cached[i] = 1;
		}
	delete[] buffer;
}
void FileManager::close(struct OnlineFile* file)
{
	lock l;
	struct FileCache* f = (struct FileCache*) file;
	if (--f->opens == 0)
	{
		//cached = 1 clean
		//cached = 2 dirty
		flush(f);
	}
}
int FileManager::del(const char* pathname)
{
	lock l;
	string path; string name;
	assert(parsePath(string(pathname),path,name));
	int ret = 0;
	if (ret = ensure_dir(path.c_str()))
		return ret;
	DirectoryCache* dir = find_dir(path.c_str())->second;
	map<string,FileCache>::iterator it = dir->files.find(name);
	if (it == dir->files.end() || it->second.opens > 0)
		return -1;
	else
	{
		for(int i=0;i<it->second.blocks;++i)
		{
			string block_pathname;
			string tmp;
			getFullname(string(pathname),block_pathname,i);
			tmp = getLocalFileName(string(pathname),i);
			if (it->second.cached[i]) unlink(tmp.c_str());//unlink
			ret |= it->second.blkstor[i]->del(block_pathname.c_str());
		}
		dir->files.erase(it);
	}
	return ret;
}
int FileManager::mkdir(const char* path,const char* name)
{
	lock l;
	int ret = 0;
	for(int i=0; i< vecStors.size();++i)
		ret |= vecStors[i]->mkdir(path,name);

	map<string,DirectoryCache*>::iterator it;
	it = find_dir(path);
	if (it != directoryCache.end())
	{
		set<string>::iterator it2 = it->second->folders.find(string(name));
		if (it2 == it->second->folders.end())
			it->second->folders.insert(string(name));
	}

	return ret;
}
struct NetFile* FileManager::lsdir(const char* path,OUT int* size /* numbner of struct NetFile */)
{
	lock l;
	int ret;
	if (ret = ensure_dir(path))
		return NULL;
	DirectoryCache* entry = find_dir(path)->second;
	int num = entry->files.size() + entry->folders.size();
	NetFile* files = new NetFile[num];
	int i=0;
	for(set<string>::iterator it = entry->folders.begin();
		it != entry->folders.end();++it	)
	{
		files[i].folder = 1;
		files[i].size = 0;
		strcpy(files[i].fileName,(*it).c_str());
		++i;
	}
	for(map<string,FileCache>::iterator it = entry->files.begin();
		it != entry->files.end(); ++it)
	{
		files[i].folder = 0;
		files[i].size = it->second.size;
		strcpy(files[i].fileName,it->first.c_str());
		++i;
	}
	*size = num;
	return files;
}
int FileManager::rmdir(const char* path,const char* name)
{
	lock l;
	int ret = 0;
	string pathname = string(path) + string(name) + "/";
	map<string,DirectoryCache*>::iterator it;
	
	for(int i=0; i< vecStors.size();++i)
		ret |= vecStors[i]->rmdir(path,name);
	
	it = find_dir(pathname.c_str());
	if (it != directoryCache.end())
		directoryCache.erase(it);

	it = find_dir(path);
	if (it != directoryCache.end())
	{
		set<string>::iterator it2 = it->second->folders.find(string(name));
		if (it2 != it->second->folders.end())
			it->second->folders.erase(it2);
	}

	return ret;
}
int FileManager::move(const char* oldFile,const char* newFile)
{
	lock l;
	int ret;
	string oldpath;string newpath;
	string oldname; string newname;
	//TODO: old name new name
	assert(parsePath(string(oldFile),oldpath,oldname));
	assert(parsePath(string(newFile),newpath,newname));
	if ( (ret = ensure_dir(oldpath.c_str())) || (ret = ensure_dir(newpath.c_str())))
		return ret;
	//begin transaction
	DirectoryCache* olddir = directoryCache[string(oldpath)];
	DirectoryCache* newdir = directoryCache[string(newpath)];
	map<string,FileCache>::iterator it;
	if ( (it = olddir->files.find(string(oldname))) == olddir->files.end() ||
		newdir->files.find(string(newname)) != newdir->files.end() )
		return -1;
	flush(&it->second);
	for(int i=0;i<it->second.blocks;++i)
	{
		//build_block_name(newfile,i,oldpathname)
		//build_block_name(oldfile,i,newpathname)
		string oldpathname;
		string newpathname;
		getFullname(string(oldFile),oldpathname,i);
		getFullname(string(newFile),newpathname,i);
		it->second.blkstor[i]->move(oldpathname.c_str(),newpathname.c_str());
		it->second.cached[i] = 0;
	}
	FileCache entry = it->second;
	olddir->files.erase(it);
	newdir->files.insert(make_pair(string(newname),entry));
	return 0;
}


map<string,struct DirectoryCache*>::iterator FileManager::find_dir(const char* path)
{
	string p(path);
	if(path[strlen(path) - 1] != '/')
		p += "/";
	return directoryCache.find(p);
}

int FileManager::ensure_dir(const char* path)
{
	string dir(path);
	int ret = 0;
	if(path[strlen(path) - 1] != '/')
		dir += "/";
	if (directoryCache.find(dir) != directoryCache.end())
		return 0;
	DirectoryCache* cache = new DirectoryCache();
	int* sizes = new int[vecStors.size()];
	struct NetFile ** files = new struct NetFile*[vecStors.size()];
	if (!files) goto cleanup;
	memset(files,0,vecStors.size() * sizeof(struct NetFile*));
	for(int i=0;i<vecStors.size();++i)
	{
		files[i] = vecStors[i]->lsdir(dir.c_str(),&sizes[i]);
		if (!files[i])
		{
			ret = -1;
			goto cleanup;
		}
		for(int j=0; j < sizes[i]; ++j)
		{
			if (files[i][j].folder && cache->folders.find(string(files[i][j].fileName)) == cache->folders.end() )
				cache->folders.insert(string(files[i][j].fileName));
			if (!files[i][j].folder)
			{
				string name;
				int blocknum;
				parseFullname(string(files[i][j].fileName),name,&blocknum);
				map<string,FileCache>::iterator it = cache->files.find(name);
				if (it == cache->files.end())
				{
					FileCache fileCache;
					fileCache.blocks = blocknum + 1;
					fileCache.blkstor = NULL;
					fileCache.size = 0;
					fileCache.opens = 0;
					fileCache.cached = NULL;
					fileCache.filename = name;
					fileCache.pathname = dir + name;
					cache->files.insert(make_pair(string(name),fileCache));
				}
				else
				{
					it->second.blocks = max(it->second.blocks,blocknum + 1);
				}
			}
		}
	}

	for(int i=0;i<vecStors.size();++i)
	{
		for(int j=0; j < sizes[i]; ++j)
		{
			string name;
			int blocknum;
			if (files[i][j].folder) continue;
			parseFullname(string(files[i][j].fileName),name,&blocknum);
			map<string,FileCache>::iterator it = cache->files.find(name);
			assert(it != cache->files.end());
			if (!it->second.blkstor)
			{
				it->second.blkstor = new OnlineStorage* [it->second.blocks];
				memset(it->second.blkstor,0,it->second.blocks * sizeof(OnlineStorage*));
				it->second.cached = new int[it->second.blocks];
				memset(it->second.cached,0,it->second.blocks*sizeof(int));
				it->second.maxblocks = it->second.blocks;
			}

			it->second.blkstor[blocknum] = vecStors[i];
			if (blocknum != it->second.blocks - 1)
			{
				if (files[i][j].size != blockSize)
					printf("warn: File %s corrupt\n",name.c_str());
				it->second.size += blockSize;
			}
			else
			{
				it->second.size += files[i][j].size;
			}
				
		}
	}

	directoryCache.insert(make_pair(dir,cache));
cleanup:
	if (files == NULL) return -1;
	for(int i=0;i<vecStors.size();++i)
		if (files[i] != NULL)
			delete[] files[i];
	if (ret)
		delete cache;
	delete[] sizes;
	delete[] files;
	return ret;
}

int FileManager::ensure_block(struct FileCache* f,int block)
{
	if (block < f->blocks && f->cached[block])
		return 0;
	if (block >= f->blocks)
	{
		if (block >= f->maxblocks)
		{
			int newblocks = f->maxblocks * 2;
			while (block >= newblocks)
				newblocks *= 2;

			OnlineStorage** newblkstor = new OnlineStorage* [newblocks];
			memset(newblkstor,0,newblocks * sizeof(OnlineStorage*));
			memcpy(newblkstor,f->blkstor, f->blocks * sizeof(OnlineStorage*));
			delete [] f->blkstor;
			f->blkstor = newblkstor;

			int* newcached = new int [newblocks];
			memset(newcached,0,newblocks * sizeof(int));
			memcpy(newcached,f->cached, f->blocks * sizeof(int));
			delete [] f->cached;
			f->cached = newcached;

		}
		ensure_block(f,f->blocks-1);
		for(int i=f->blocks-1;i<=block;++i)
		{
			if (!f->blkstor[i])
				f->blkstor[i] = vecStors[rand() % vecStors.size()];
			f->cached[i] = 2;
		}
		f->blocks = block + 1;
		f->size = max(f->size,blockSize * (f->blocks - 1));
	}
	string tmp; string pathname;
	char* buffer = new char[blockSize];
	memset(buffer,0,blockSize);
	int size = block == f->blocks-1 ? f->size % blockSize : blockSize;
	//TODO: gettmpname pathname
	tmp = getLocalFileName(f->pathname,block);
	assert(getFullname(f->pathname,pathname,block));
	if (size && f->blkstor[block]->get(pathname.c_str(),buffer,size))
	{
		delete[] buffer;
		return -1;
	}
	FILE* fp = fopen(tmp.c_str(),"wb");
	fwrite(buffer,1,blockSize,fp);
	fclose(fp);
	f->cached[block] = 1;
	if (!f->blkstor[block])
		f->blkstor[block] = vecStors[rand() % vecStors.size()];
	delete[] buffer;
	return 0;
}

int FileManager::fileinfo(const char* pathname,int* size,int* folder)
{
	lock l;
	string path; string name;
	assert(parsePath(string(pathname),path,name));
	int ret = 0;
	if (ret = ensure_dir(path.c_str()))
		return ret;
	DirectoryCache* dir = find_dir(path.c_str())->second;
	map<string,FileCache>::iterator it = dir->files.find(name);
	if (it == dir->files.end())
	{
		set<string>::iterator it2 = dir->folders.find(name);
		if (it2 == dir->folders.end())
			return -1;
		else
		{
			*size = 0;
			*folder = 1;
		}
	}
	else
	{
		*size = it->second.size;
		*folder = 0;
	}
	return ret;
}